// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

#include "stdafx.h"
#include "WorldBuilder.h"
#include "PropertiesWnd.h"
#include "Resource.h"
#include "NodeManager.h"
#include "ActionManager.h"
#include "GeometryNode.h"
#include "LightNode.h"
#include "Selectable.h"
#include "Material.h"
#include "AnimationManager.h"
#include "MainView.h" // Remove this
#include "Common.h"
#include "GeometryFactory.h"
#include "ActionShortcuts.h"
#include "Shader.h"
#include "ShaderProgram.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
// User-defined messages
// See this http://vcfaq.mvps.org/mfc/12.htm

// The main reason we use user-defined messages is because it's the easiest
// way to update the controls from worker threads

#define WM_ED3D_UPDATE_ACTIVE_PROPERTY_LIST (WM_APP + 1)

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

CPropertiesWnd::CPropertiesWnd()
: m_activePropertyType(PropertyTypes::NOT_INIT),
  WorldFollower(WorldFollower::IsNodeListener),
  UiFollower(UiFollower::IsSelectionListener),
  m_lastUpdateMillis(0.)
{
}

CPropertiesWnd::~CPropertiesWnd()
{
}

BEGIN_MESSAGE_MAP(CPropertiesWnd, CDockablePane)
    ON_WM_CREATE()
    ON_WM_SIZE()
    ON_COMMAND(ID_EXPAND_ALL, OnExpandAllProperties)
    ON_UPDATE_COMMAND_UI(ID_EXPAND_ALL, OnUpdateExpandAllProperties)
    ON_COMMAND(ID_SORTPROPERTIES, OnSortProperties)
    ON_UPDATE_COMMAND_UI(ID_SORTPROPERTIES, OnUpdateSortProperties)
    ON_WM_SETFOCUS()
    ON_WM_SETTINGCHANGE()
    ON_REGISTERED_MESSAGE( AFX_WM_PROPERTY_CHANGED, OnPropertyChanged)
    ON_MESSAGE(WM_ED3D_UPDATE_ACTIVE_PROPERTY_LIST, OnUpdateActivePropertyList)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CResourceViewBar message handlers

void CPropertiesWnd::AdjustLayout()
{
    if (GetSafeHwnd() == NULL)
    {
        return;
    }

    CRect rectClient, rectCombo;
    GetClientRect(rectClient);

    m_wndObjectCombo.GetWindowRect(&rectCombo);

    int toolbarHeight = m_wndToolBar.CalcFixedLayout(FALSE, TRUE).cy;

    m_wndObjectCombo.SetWindowPos(NULL, 
        rectClient.left,        // left
        rectClient.top,         // top
        rectClient.Width(),     // width
        200,                    // height
        SWP_NOACTIVATE | SWP_NOZORDER);
    
    int comboHeight = rectCombo.Size().cy;
    m_wndToolBar.SetWindowPos(NULL, 
        rectClient.left,                // left
        rectClient.top + comboHeight,   // top
        rectClient.Width(),             // width
        toolbarHeight,                  // height
        SWP_NOACTIVATE | SWP_NOZORDER);

    int matPreviewWndHeight;

    Selectable* selection = 
        hasUi() ? getUi().getSelectionManager().getSelection() : NULL;
    Material* mat = dynamic_cast<Material*>(selection);
    if(mat != NULL)
    {
        matPreviewWndHeight = rectClient.Width();
        m_wndMaterialPreview.SetWindowPos(NULL, 
            rectClient.left,                                // left
            rectClient.top + comboHeight + toolbarHeight,   // top
            rectClient.Width(),                             // width
            matPreviewWndHeight,                            // height
            SWP_NOACTIVATE | SWP_NOZORDER);
        m_wndMaterialPreview.ShowWindow(SW_SHOW);
    }
    else
    {
        matPreviewWndHeight = 0;
        m_wndMaterialPreview.ShowWindow(SW_HIDE);
    }

    int heightLeftForPropListWnd = comboHeight + toolbarHeight 
                                    + matPreviewWndHeight;

    for(int i = 0; i < PropertyTypes::COUNT; ++i)
    {
        m_wndPropList[i].SetWindowPos(
            NULL, rectClient.left,                              // left
            rectClient.top + heightLeftForPropListWnd,          // top
            rectClient.Width(),                                 // width
            rectClient.Height() - heightLeftForPropListWnd,     // height
            SWP_NOACTIVATE | SWP_NOZORDER);
    }
}


int CPropertiesWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CDockablePane::OnCreate(lpCreateStruct) == -1)
        return -1;

    CRect rectDummy;
    rectDummy.SetRectEmpty();

    // Create combo:
    DWORD dwViewStyle = WS_CHILD | WS_VISIBLE
        | CBS_DROPDOWNLIST | WS_BORDER | CBS_SORT | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;

    int currentId = 1;
    if (!m_wndObjectCombo.Create(dwViewStyle, rectDummy, this, currentId++))
    {
        TRACE0("Failed to create Properties Combo \n");
        return -1;      // fail to create
    }

    m_wndObjectCombo.AddString(_T("Application"));
    m_wndObjectCombo.AddString(_T("Properties Window"));
    m_wndObjectCombo.SetFont(CFont::FromHandle((HFONT)GetStockObject(DEFAULT_GUI_FONT)));
    m_wndObjectCombo.SetCurSel(0);

    // TODO create this window only when needed
    DWORD style = WS_CHILD | WS_VISIBLE | WS_BORDER;
    if(!m_wndMaterialPreview.Create(style, rectDummy, this, currentId++))
    {
        TRACE0("Failed to create OpenGL Material Preview Window\n");
        return -1;      
    }

    for(int i = 0; i < PropertyTypes::COUNT; ++i)
    {
        if (!m_wndPropList[i].Create(WS_VISIBLE | WS_CHILD, 
                                     rectDummy, this, currentId++))
        {
            TRACE0("Failed to create Properties Grid \n");
            return -1;      // fail to create
        }
    }

    InitPropList();

    m_wndToolBar.Create(this, AFX_DEFAULT_TOOLBAR_STYLE, IDR_PROPERTIES);
    m_wndToolBar.LoadToolBar(IDR_PROPERTIES, 0, 0, TRUE /* Is locked */);
    m_wndToolBar.CleanUpLockedImages();
    m_wndToolBar.LoadBitmap(theApp.m_bHiColorIcons ? IDB_PROPERTIES_HC : IDR_PROPERTIES, 0, 0, TRUE /* Locked */);

    m_wndToolBar.SetPaneStyle(m_wndToolBar.GetPaneStyle() | CBRS_TOOLTIPS | CBRS_FLYBY);
    m_wndToolBar.SetPaneStyle(m_wndToolBar.GetPaneStyle() & ~(CBRS_GRIPPER | CBRS_SIZE_DYNAMIC | CBRS_BORDER_TOP | CBRS_BORDER_BOTTOM | CBRS_BORDER_LEFT | CBRS_BORDER_RIGHT));
    m_wndToolBar.SetOwner(this);

    // All commands will be routed via this control, not via the parent frame:
    m_wndToolBar.SetRouteCommandsViaFrame(FALSE);

    AdjustLayout();

    return 0;
}

void CPropertiesWnd::OnSize(UINT nType, int cx, int cy)
{
    CDockablePane::OnSize(nType, cx, cy);
    AdjustLayout();
}

CMFCPropertyGridCtrl * CPropertiesWnd::activePropList()
{
    ASSERT(m_activePropertyType >= PropertyTypes::NOTHING && 
        m_activePropertyType < PropertyTypes::COUNT);
    return &m_wndPropList[m_activePropertyType];
}

void CPropertiesWnd::OnExpandAllProperties()
{
    activePropList()->ExpandAll();
}

void CPropertiesWnd::OnUpdateExpandAllProperties(CCmdUI* /*pCmdUI*/)
{
}

void CPropertiesWnd::OnSortProperties()
{
    activePropList()->SetAlphabeticMode(!activePropList()->IsAlphabeticMode());
}

void CPropertiesWnd::OnUpdateSortProperties(CCmdUI* pCmdUI)
{
    if(m_activePropertyType == PropertyTypes::NOTHING)
    {
        pCmdUI->Enable(false);
    }
    else
    {
        pCmdUI->Enable(true);
        pCmdUI->SetCheck(activePropList()->IsAlphabeticMode());
    }
}

namespace
{

PropertyType selectionToType(Selectable* item)
{
    if(dynamic_cast<GeometryNode*>(item) != NULL)
    {
        return PropertyTypes::SHAPE;
    }

    if(dynamic_cast<LightNode*>(item) != NULL)
    {
        return PropertyTypes::LIGHT;
    }

    if(dynamic_cast<Material*>(item) != NULL)
    {
        return PropertyTypes::MATERIAL;
    }

    if(dynamic_cast<ShaderProgram*>(item) != NULL)
    {
        return PropertyTypes::SHADER_PROGRAM;
    }

    if(dynamic_cast<Shader*>(item) != NULL)
    {
        return PropertyTypes::SHADER;
    }

    if(dynamic_cast<Texture*>(item) != NULL)
    {
        return PropertyTypes::TEXTURE;
    }

    if(dynamic_cast<TextureLayer*>(item) != NULL)
    {
        return PropertyTypes::TEXTURE_LAYER;
    }

    if(dynamic_cast<UniformVar*>(item) != NULL)
    {
        return PropertyTypes::UNIFORM_VAR;
    }

    return PropertyTypes::NOTHING;
}

}

void CPropertiesWnd::updateActivePropertyList()
{
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // Update only every N milliseconds (otherwise the framerate
    // drops too much during drag cycles)
    
    double currentMillis = theApp.getTimeMicro() / 1000.;
    double delta = currentMillis - m_lastUpdateMillis;
    if(delta < 330.) // 1/3rd of a second
    {
        return;
    }
    m_lastUpdateMillis = currentMillis;

    ::PostMessage(*this, WM_ED3D_UPDATE_ACTIVE_PROPERTY_LIST, 
                  WPARAM(0), LPARAM(0));
}

void CPropertiesWnd::updateGeometryOrientationControls(const gx::Mat4& transform)
{
    gx::Vec3 euler = transform.rotation.to_euler();
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        m_appearanceOrientationPGP[dim]->SetValue(short(euler[dim] * 180. / PI));
    }
}

void CPropertiesWnd::updateGeometryPositionControls(const gx::Mat4& transform)
{   
    gx::Vec3 pos = transform.translation;
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        m_appearancePositionPGP[dim]->SetValue(float(pos[dim]));
    }
}

void CPropertiesWnd::updateGeometryScaleControls(const gx::Mat4& transform)
{
    gx::Vec3 scale = transform.rotation.get_scale();
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        m_appearanceScalePGP[dim]->SetValue(float(scale[dim]));
    }
}

void CPropertiesWnd::updateGeometryPropertyList(const GeometryNode& node)
{
    std::string ansi = GeometryFactory::nameFromGeometry(*node.getGeometry().get());
    CString unicode(CA2T(ansi.c_str()));
    m_appearanceTypePGP->SetValue(unicode);

    //m_appearanceWireframePGP->SetValue((_variant_t)node.getWireframe());
    //m_appearanceFrontMaterialPGP->SetValue(short(node.getMaterialId(GeometrySides::Front)));
    //m_appearanceBackMaterialPGP->SetValue(short(node.getMaterialId(GeometrySides::Back)));
    //m_appearanceShaderPGP->SetValue(short(node.getShaderId()));

    gx::Mat4 transform = node.getLocalTransform();
    updateGeometryScaleControls(transform);
    updateGeometryOrientationControls(transform);
    updateGeometryPositionControls(transform);
}

void CPropertiesWnd::updateLightPropertyList(const LightNode& light)
{
    m_lightAmbientColorPGP->SetColor(convertColor(light.getAmbientColor()));
    m_lightDiffuseColorPGP->SetColor(convertColor(light.getDiffuseColor()));
    m_lightSpecularColorPGP->SetColor(convertColor(light.getSpecularColor()));
    
    bool isPos = light.getType() == LightTypes::POSITIONAL;
    m_lightTypePGP->SetValue(isPos ? _T("Positional") : _T("Directional"));
    
    m_lightSpotGroup->Show(isPos, true);
    bool isSpot = light.isSpot();
    m_lightSpotEnablePGP->SetValue(isSpot ? _T("Yes") : _T("No"));
    m_lightSpotCutoffPGP->Show(isSpot);
    if(isSpot)
    {
        m_lightSpotCutoffPGP->SetValue((short)light.getSpotCutoff());
    }

    m_lightAttenuationGroup->Show(isPos, true);
    if(isPos)
    {
        m_lightConstantAttenuationPGP->SetValue(
            short(light.getConstantAttenuationFactor() * 100.f));
        m_lightLinearAttenuationPGP->SetValue(
            short(light.getLinearAttenuationFactor() * 100.f));
        m_lightQuadraticAttenuationPGP->SetValue(
            short(light.getQuadraticAttenuationFactor() * 100.f));
    }
}

void CPropertiesWnd::updateMaterialPropertyList(const Material& mat)
{
    m_materialIdPGP->SetValue(short(mat.getId()));

    m_materialAmbientColorPGP->SetColor(convertColor(mat.getAmbientColor()));
    m_materialDiffuseColorPGP->SetColor(convertColor(mat.getDiffuseColor()));
    m_materialSpecularColorPGP->SetColor(convertColor(mat.getSpecularColor()));
    
    m_materialShininessPGP->SetValue(short(mat.getShininess() * 100.));

    m_materialTransparentPGP->SetValue((_variant_t)mat.isTransparent());
}

void CPropertiesWnd::updateShaderProgramPropertyList(const ShaderProgram& program)
{
    m_shaderProgramIdPGP->SetValue(short(program.getId()));
    m_shaderProgramLinksPGP->SetValue(program.links() ? _T("true") : _T("false"));
}

void CPropertiesWnd::updateShaderPropertyList(const Shader& shader)
{
    m_shaderIdPGP->SetValue(short(shader.getId()));
    m_shaderCompilesPGP->SetValue(shader.compiles() ? _T("true") : _T("false"));
}

void CPropertiesWnd::updateTexturePropertyList(const Texture& tex)
{
    m_textureIdPGP->SetValue(short(tex.getId()));
    
    CString unicode(CA2T(tex.getFileName().c_str()));
    m_textureFileNamePGP->SetValue(unicode);
}

// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

namespace 
{

typedef std::map<std::string, EnvironmentMode> EnvModeMap;
const EnvModeMap& getEnvModes()
{
    static EnvModeMap modes;
    if(modes.empty())
    {
        modes["Replace"] = EnvironmentModes::Replace;
        modes["Modulate"] = EnvironmentModes::Modulate;
        modes["Decal"] = EnvironmentModes::Decal;
        modes["Add"] = EnvironmentModes::Add;
        modes["Blend"] = EnvironmentModes::Blend;
        modes["Combine"] = EnvironmentModes::Combine;
    }
    return modes;
}

typedef std::map<std::string, RgbCombinationMode> RgbCombiModeMap;
const RgbCombiModeMap& getRgbCombiModes()
{
    static RgbCombiModeMap modes;
    if(modes.empty())
    {
        modes["Replace"] = RgbCombinationModes::Replace;
        modes["Modulate"] = RgbCombinationModes::Modulate;
        modes["Alpha Interp."] = RgbCombinationModes::AlphaInterpolation;
        modes["Add"] = RgbCombinationModes::Add;
        modes["Add Signed"] = RgbCombinationModes::AddSigned;
        modes["Subtract"] = RgbCombinationModes::Subtract;
    }
    return modes;
}


typedef std::map<std::string, AlphaCombinationMode> AlphaCombiModeMap;
const AlphaCombiModeMap& getAlphaCombiModes()
{
    static AlphaCombiModeMap modes;
    if(modes.empty())
    {
        modes["Replace"] = AlphaCombinationModes::Replace;
        modes["Modulate"] = AlphaCombinationModes::Modulate;
        modes["Add"] = AlphaCombinationModes::Add;
        modes["Add Signed"] = AlphaCombinationModes::AddSigned;
        modes["Subtract"] = AlphaCombinationModes::Subtract;
    }
    return modes;
}

typedef std::map<std::string, TextureWrapMode> TextureWrapModeMap;
const TextureWrapModeMap& getTextureWrapModes()
{
    static TextureWrapModeMap modes;
    if(modes.empty())
    {
        modes["Repeat"] = TextureWrapModes::Repeat;
        modes["Mirrored Repeat"] = TextureWrapModes::MirroredRepeat;
        modes["Clamp"] = TextureWrapModes::Clamp;
        modes["Clamp to Border"] = TextureWrapModes::ClampToBorder;
        modes["Clamp to Edge"] = TextureWrapModes::ClampToEdge;
    }
    return modes;
}

} // Anonymous namespace

void CPropertiesWnd::updateTexLayerTexIdField(const TextureLayer& texLayer)
{
    Texture* tex = texLayer.getTexture();
    m_texLayerTexIdPGP->SetValue((short)tex->getId());
}

void CPropertiesWnd::updateTexLayerEnvModeField(const TextureLayer& texLayer)
{
    const EnvModeMap& modes = getEnvModes();
    EnvModeMap::const_iterator it = modes.begin();
    for(; it != modes.end(); ++it)
    {
        if(it->second == texLayer.getEnvMode())
        {
            CString unicode(CA2T(it->first.c_str()));
            m_texLayerEnvModePGP->SetValue(unicode);
            break;
        }
    }
    ASSERT(it != modes.end());
}

void CPropertiesWnd::updateTexLayerRgbCombiModeField(const TextureLayer& texLayer)
{
    const RgbCombiModeMap& modes = getRgbCombiModes();
    RgbCombiModeMap::const_iterator it = modes.begin();
    for(; it != modes.end(); ++it)
    {
        if(it->second == texLayer.getRgbCombiMode())
        {
            CString unicode(CA2T(it->first.c_str()));
            m_texLayerRgbCombiModePGP->SetValue(unicode);
            break;
        }
    }
    ASSERT(it != modes.end());
}

void CPropertiesWnd::updateTexLayerAlphaCombiModeField(const TextureLayer& texLayer)
{
    const AlphaCombiModeMap& modes = getAlphaCombiModes();
    AlphaCombiModeMap::const_iterator it = modes.begin();
    for(; it != modes.end(); ++it)
    {
        if(it->second == texLayer.getAlphaCombiMode())
        {
            CString unicode(CA2T(it->first.c_str()));
            m_texLayerAlphaCombiModePGP->SetValue(unicode);
            break;
        }
    }
    ASSERT(it != modes.end());
}

void CPropertiesWnd::updateTexLayerWrapFields(const TextureLayer& texLayer)
{
    const TextureWrapModeMap& modes = getTextureWrapModes();
    TextureWrapModeMap::const_iterator it = modes.begin();
    for(; it != modes.end(); ++it)
    {
        if(it->second == texLayer.getSWrapMode())
        {
            CString unicode(CA2T(it->first.c_str()));
            m_texLayerSWrapModePGP->SetValue(unicode);
            break;
        }
    }
    ASSERT(it != modes.end());
    for(it = modes.begin(); it != modes.end(); ++it)
    {
        if(it->second == texLayer.getTWrapMode())
        {
            CString unicode(CA2T(it->first.c_str()));
            m_texLayerTWrapModePGP->SetValue(unicode);
            break;
        }
    }    
    ASSERT(it != modes.end());
}

void CPropertiesWnd::updateTexLayerBorderColorField(const TextureLayer& texLayer)
{
    m_texLayerBorderColorPGP->SetColor(convertColor(texLayer.getBorderColor()));
}

void CPropertiesWnd::updateTextureLayerPropertyList(const TextureLayer& texLayer)
{
    updateTexLayerTexIdField(texLayer);
    updateTexLayerEnvModeField(texLayer);
    updateTexLayerRgbCombiModeField(texLayer);
    updateTexLayerAlphaCombiModeField(texLayer);
    updateTexLayerWrapFields(texLayer);
    updateTexLayerBorderColorField(texLayer);
}

void CPropertiesWnd::updateUniformVarPropertyList(const UniformVar& uniVar)
{
    CA2T converter(UniformVar::typeToTypeName(uniVar.getType()).c_str());
    CString unicode(converter);
    m_uniVarTypePGP->SetValue(unicode);

    size_t numFloats = uniVar.getNumFloats();
    ASSERT(numFloats <= MaxUniformVarFloats);
    for(size_t i = 0; i < MaxUniformVarFloats; ++i)
    {
        m_uniVarFloatPGP[i]->Show(i < numFloats, true);
    }

    const GLfloat* floats = uniVar.getFloats();
    for(size_t i = 0; i < numFloats; ++i)
    {
        m_uniVarFloatPGP[i]->SetValue(_variant_t((float)floats[i]));
    }
}

LRESULT CPropertiesWnd::OnUpdateActivePropertyList(WPARAM, LPARAM)
{
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    // Recompute the layout when selection type changes (this is mainly
    // to make the material preview window appear/disappear)

    Selectable* selection = hasUi() ? 
        getUi().getSelectionManager().getSelection() : NULL;

    PropertyType type = selectionToType(selection);
    if(type != m_activePropertyType)
    {
        AdjustLayout();
    }

    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
    
    m_activePropertyType = type;

    if(m_activePropertyType == PropertyTypes::SHAPE)
    {
        GeometryNode* node = dynamic_cast<GeometryNode*>(selection);
        ASSERT(node != NULL);
        updateGeometryPropertyList(*node);
    }
    
    else if(m_activePropertyType == PropertyTypes::LIGHT)
    {
        LightNode* node = dynamic_cast<LightNode*>(selection);
        ASSERT(node != NULL);
        updateLightPropertyList(*node);
    }

    else if(m_activePropertyType == PropertyTypes::MATERIAL)
    {
        Material* mat = dynamic_cast<Material*>(selection);
        ASSERT(mat != NULL);
        updateMaterialPropertyList(*mat);
    }

    else if(m_activePropertyType == PropertyTypes::SHADER_PROGRAM)
    {
        ShaderProgram* program = dynamic_cast<ShaderProgram*>(selection);
        ASSERT(program != NULL);
        updateShaderProgramPropertyList(*program);
    }

    else if(m_activePropertyType == PropertyTypes::SHADER)
    {
        Shader* shader = dynamic_cast<Shader*>(selection);
        ASSERT(shader != NULL);
        updateShaderPropertyList(*shader);
    }

    else if(m_activePropertyType == PropertyTypes::TEXTURE)
    {
        Texture* tex = dynamic_cast<Texture*>(selection);
        ASSERT(tex != NULL);
        updateTexturePropertyList(*tex);
    }
    
    else if(m_activePropertyType == PropertyTypes::TEXTURE_LAYER)
    {
        TextureLayer* texLayer = dynamic_cast<TextureLayer*>(selection);
        ASSERT(texLayer != NULL);
        updateTextureLayerPropertyList(*texLayer);
    }

    else if(m_activePropertyType == PropertyTypes::UNIFORM_VAR)
    {
        UniformVar* uniVar = dynamic_cast<UniformVar*>(selection);
        ASSERT(uniVar != NULL);
        updateUniformVarPropertyList(*uniVar);
    }

    else
    {
        m_activePropertyType = PropertyTypes::NOTHING;
    }

    for(int i = 0; i < PropertyTypes::COUNT; ++i)
    {
        m_wndPropList[i].ShowWindow(i == m_activePropertyType ? SW_SHOW : SW_HIDE);
    }
    
    return 0; // I handled this message
}


// = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


void CPropertiesWnd::createNothingPropertyList()
{
    // TODO use a label control here
    m_wndPropList[PropertyTypes::NOTHING].AddProperty(
        new CMFCPropertyGridProperty(_T("(No selection)"), 0, TRUE));
}

void CPropertiesWnd::createShapePropertyList()
{
    // ************************************************************************
    // Appearance group
    
    m_appearanceTypePGP = new CMFCPropertyGridProperty(
        _T("Type"), _T("Cylinder"), _T("One of: Cylinder, Sphere, or Cube"));

    m_appearanceTypePGP->AddOption(_T("Cylinder"));
    m_appearanceTypePGP->AddOption(_T("Sphere"));
    m_appearanceTypePGP->AddOption(_T("Cube"));
    m_appearanceTypePGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::SHAPE].AddProperty(m_appearanceTypePGP);

    m_appearanceWireframePGP = new CMFCPropertyGridProperty(
        _T("Wireframe"), (_variant_t)false, 
        _T("Specifies that the node will be rendered in wireframe"));
    m_wndPropList[PropertyTypes::SHAPE].AddProperty(m_appearanceWireframePGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    // Material + Shader

    CMFCPropertyGridProperty* materialGroup = 
        new CMFCPropertyGridProperty(_T("Material"));

    m_appearanceFrontMaterialPGP = new CMFCPropertyGridProperty(
        _T("Front Material Id"), _variant_t(short(0)), _T("Specifies the shape's front material id"));
    materialGroup->AddSubItem(m_appearanceFrontMaterialPGP);

    m_appearanceBackMaterialPGP = new CMFCPropertyGridProperty(
        _T("Back Material Id"), _variant_t(short(0)), _T("Specifies the shape's back material id"));
    materialGroup->AddSubItem(m_appearanceBackMaterialPGP);

    m_appearanceShaderPGP = new CMFCPropertyGridProperty(
        _T("Shader Id"), _variant_t(short(0)), _T("Specifies the shape's shader id"));
    materialGroup->AddSubItem(m_appearanceShaderPGP);

    m_wndPropList[PropertyTypes::SHAPE].AddProperty(materialGroup);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    // Scale subgroup 

    CMFCPropertyGridProperty* scaleGroup = 
        new CMFCPropertyGridProperty(_T("Scale"), 0, TRUE);
    scaleGroup->AllowEdit(false);

    // X, Y and Z items of scale subgroup
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        CString dimName(TCHAR('X' + dim));
        CString description;
        description.Format(_T("Specifies the node's %s scale"), dimName);
        m_appearanceScalePGP[dim] = 
            new CMFCPropertyGridProperty(dimName, _variant_t(0.f), 
                                         description);
        scaleGroup->AddSubItem(m_appearanceScalePGP[dim]);
    }
    m_wndPropList[PropertyTypes::SHAPE].AddProperty(scaleGroup);
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    // Orientation subgroup 

    CMFCPropertyGridProperty* orientationGroup = 
        new CMFCPropertyGridProperty(_T("Orientation"), 0, TRUE);
    orientationGroup->AllowEdit(false);

    // X, Y and Z items of orientation subgroup
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        CString dimName(TCHAR('X' + dim));
        CString description;
        description.Format(_T("Specifies the node's %s orientation"), 
                           dimName);
        m_appearanceOrientationPGP[dim] =
            new CMFCPropertyGridProperty(dimName, _variant_t(short(0)), 
                                         description);
        m_appearanceOrientationPGP[dim]->EnableSpinControl(TRUE, -180, 180);
        orientationGroup->AddSubItem(m_appearanceOrientationPGP[dim]);
    }
    m_wndPropList[PropertyTypes::SHAPE].AddProperty(orientationGroup);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
    // Position subgroup 

    CMFCPropertyGridProperty * positionGroup = 
        new CMFCPropertyGridProperty(_T("Position"), 0, TRUE);
    positionGroup->AllowEdit(false);

    // X, Y and Z items of position subgroup
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        CString dimName(TCHAR('X' + dim));
        CString description;
        description.Format(_T("Specifies the node's %s position"), dimName);
        m_appearancePositionPGP[dim] = 
            new CMFCPropertyGridProperty(dimName, _variant_t(0.f),
                                         description);
        positionGroup->AddSubItem(m_appearancePositionPGP[dim]);
    }
    m_wndPropList[PropertyTypes::SHAPE].AddProperty(positionGroup);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
}


void CPropertiesWnd::createLightPropertyList()
{
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The light type (positional, directional) property

    m_lightTypePGP = new CMFCPropertyGridProperty(
        _T("Type"), _T(""), _T("Either positional o directional"));
    m_lightTypePGP->AddOption(_T("Positional"));
    m_lightTypePGP->AddOption(_T("Directional"));
    m_lightTypePGP->AllowEdit(false);

    m_wndPropList[PropertyTypes::LIGHT].AddProperty(m_lightTypePGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

    // The ambient color property

    m_lightAmbientColorPGP = new CMFCPropertyGridColorProperty(
        _T("Ambient Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the lights's ambient color"));

    m_lightAmbientColorPGP->EnableOtherButton(_T("Other..."));

    // TODO use a real default color here
    m_lightAmbientColorPGP->EnableAutomaticButton(
        _T("Default"), ::GetSysColor(COLOR_3DFACE));

    m_wndPropList[PropertyTypes::LIGHT].AddProperty(m_lightAmbientColorPGP);
    
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The diffuse color property

    m_lightDiffuseColorPGP = new CMFCPropertyGridColorProperty(
        _T("Diffuse Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the lights's diffuse color"));

    m_lightDiffuseColorPGP->EnableOtherButton(_T("Other..."));

    // TODO use a real default color here
    m_lightDiffuseColorPGP->EnableAutomaticButton(
        _T("Default"), ::GetSysColor(COLOR_3DFACE));

    m_wndPropList[PropertyTypes::LIGHT].AddProperty(m_lightDiffuseColorPGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The specular color property

    m_lightSpecularColorPGP = new CMFCPropertyGridColorProperty(
        _T("Specular Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the lights's specular color"));

    m_lightSpecularColorPGP->EnableOtherButton(_T("Other..."));

    // TODO use a real default color here
    m_lightSpecularColorPGP->EnableAutomaticButton(
        _T("Default"), ::GetSysColor(COLOR_3DFACE));

    m_wndPropList[PropertyTypes::LIGHT].AddProperty(m_lightSpecularColorPGP);

     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The spot light group

    m_lightSpotGroup = new CMFCPropertyGridProperty(_T("Spot Light"));
    
    // "Enabled" field
    m_lightSpotEnablePGP = new CMFCPropertyGridProperty(
        _T("Enabled"), _T(""), _T("Makes this light a spot light"));
    m_lightSpotEnablePGP->AddOption(_T("Yes"));
    m_lightSpotEnablePGP->AddOption(_T("No"));
    m_lightSpotEnablePGP->AllowEdit(false);
    m_lightSpotGroup->AddSubItem(m_lightSpotEnablePGP);

    // "Cutoff" field
    m_lightSpotCutoffPGP = new CMFCPropertyGridProperty(
        _T("Cutoff"), _variant_t(short(0)), 
        _T("Change the cutoff angle of the spot light (0-90)"));
    m_lightSpotCutoffPGP->EnableSpinControl(true, 0, 90);
    m_lightSpotGroup->AddSubItem(m_lightSpotCutoffPGP);

    m_wndPropList[PropertyTypes::LIGHT].AddProperty(m_lightSpotGroup);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The attenuation group

    m_lightAttenuationGroup = new CMFCPropertyGridProperty(_T("Attenuation terms (x100)"));
    
    // "Constant" field
    m_lightConstantAttenuationPGP = new CMFCPropertyGridProperty(
        _T("Constant"), _variant_t(short(0)),
        _T("Sets the constant attenuation factor for this light"));
    m_lightConstantAttenuationPGP->EnableSpinControl(true, 0, 1000);
    m_lightAttenuationGroup->AddSubItem(m_lightConstantAttenuationPGP);

    // "Linear" field
    m_lightLinearAttenuationPGP = new CMFCPropertyGridProperty(
        _T("Linear"), _variant_t(short(0)),
        _T("Sets the linear attenuation factor for this light"));
    m_lightLinearAttenuationPGP->EnableSpinControl(true, 0, 1000);
    m_lightAttenuationGroup->AddSubItem(m_lightLinearAttenuationPGP);

    // "Quadratic" field
    m_lightQuadraticAttenuationPGP = new CMFCPropertyGridProperty(
        _T("Quadratic"), _variant_t(short(0)),
        _T("Sets the quadratic attenuation factor for this light"));
    m_lightQuadraticAttenuationPGP->EnableSpinControl(true, 0, 1000);
    m_lightAttenuationGroup->AddSubItem(m_lightQuadraticAttenuationPGP);

    m_wndPropList[PropertyTypes::LIGHT].AddProperty(m_lightAttenuationGroup);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    
}

void CPropertiesWnd::createMaterialPropertyList()
{
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The id property

    m_materialIdPGP = new CMFCPropertyGridProperty(
        _T("ID"), _variant_t(short(0)), 
        _T("Shows the materials's id"));
    m_materialIdPGP->AllowEdit(false);
    
    m_wndPropList[PropertyTypes::MATERIAL].AddProperty(m_materialIdPGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The ambient color property

    m_materialAmbientColorPGP = new CMFCPropertyGridColorProperty(
        _T("Ambient Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the materials's ambient color"));
    m_materialAmbientColorPGP->EnableOtherButton(_T("Other..."));

    // TODO use a real default color here
    m_materialAmbientColorPGP->EnableAutomaticButton(
        _T("Default"), ::GetSysColor(COLOR_3DFACE));

    m_wndPropList[PropertyTypes::MATERIAL].AddProperty(m_materialAmbientColorPGP);
    
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The diffuse color property

    m_materialDiffuseColorPGP = new CMFCPropertyGridColorProperty(
        _T("Diffuse Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the materials's diffuse color"));
    m_materialDiffuseColorPGP->EnableOtherButton(_T("Other..."));

    // TODO use a real default color here
    m_materialDiffuseColorPGP->EnableAutomaticButton(
        _T("Default"), ::GetSysColor(COLOR_3DFACE));

    m_wndPropList[PropertyTypes::MATERIAL].AddProperty(m_materialDiffuseColorPGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The specular color property

    m_materialSpecularColorPGP = new CMFCPropertyGridColorProperty(
        _T("Specular Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the materials's specular color"));
    m_materialSpecularColorPGP->EnableOtherButton(_T("Other..."));

    // TODO use a real default color here
    m_materialSpecularColorPGP->EnableAutomaticButton(
        _T("Default"), ::GetSysColor(COLOR_3DFACE));

    m_wndPropList[PropertyTypes::MATERIAL].AddProperty(m_materialSpecularColorPGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The shininess property

    m_materialShininessPGP = new CMFCPropertyGridProperty(
        _T("Shininess"), _variant_t(short(0)), 
        _T("Specifies the materials's shininess (0-100)"));
    m_materialShininessPGP->EnableSpinControl(true, 0, 100);
    m_wndPropList[PropertyTypes::MATERIAL].AddProperty(m_materialShininessPGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The "transparent" property

    m_materialTransparentPGP = new CMFCPropertyGridProperty(
        _T("Transparent"), (_variant_t)false, 
        _T("Specifies whether the material will be rendered with transparency enabled"));
    m_wndPropList[PropertyTypes::MATERIAL].AddProperty(m_materialTransparentPGP);
}

void CPropertiesWnd::createShaderProgramPropertyList()
{
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The id property

    m_shaderProgramIdPGP = new CMFCPropertyGridProperty(
        _T("ID"), _variant_t(short(0)), _T("Shows the shader program's id"));
    m_shaderProgramIdPGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::SHADER_PROGRAM].AddProperty(m_shaderProgramIdPGP);

    // For some reason MFC does not allow a non-editable bool property
    m_shaderProgramLinksPGP = new CMFCPropertyGridProperty(
        _T("Links"), _T(""), _T("True if the shader program links correctly"));
    m_shaderProgramLinksPGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::SHADER_PROGRAM].AddProperty(m_shaderProgramLinksPGP);
}

void CPropertiesWnd::createShaderPropertyList()
{
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The id property

    m_shaderIdPGP = new CMFCPropertyGridProperty(
        _T("ID"), _variant_t(short(0)), _T("Shows the shader's id"));
    m_shaderIdPGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::SHADER].AddProperty(m_shaderIdPGP);

    // For some reason MFC does not allow a non-editable bool property
    m_shaderCompilesPGP = new CMFCPropertyGridProperty(
        _T("Compiles"), _T(""), _T("True if the shader  compiles correctly"));
    m_shaderCompilesPGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::SHADER].AddProperty(m_shaderCompilesPGP);
}

void CPropertiesWnd::createTexturePropertyList()
{
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The id property

    m_textureIdPGP = new CMFCPropertyGridProperty(
        _T("ID"), _variant_t(short(0)), _T("Shows the texture's id"));
    m_textureIdPGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::TEXTURE].AddProperty(m_textureIdPGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The file name property

    m_textureFileNamePGP = new CMFCPropertyGridProperty(
        _T("File"), _T(""), _T("Shows the texture's file name"));
    m_textureFileNamePGP->AllowEdit(false);
    m_wndPropList[PropertyTypes::TEXTURE].AddProperty(m_textureFileNamePGP);
}


void CPropertiesWnd::createTextureLayerPropertyList()
{
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The environment mode property

    m_texLayerTexIdPGP = new CMFCPropertyGridProperty(
        _T("Texture Id"), _variant_t(short(0)), 
        _T("Shows the texture layer's texture id"));
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerTexIdPGP);
    
    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The environment mode property

    m_texLayerEnvModePGP = new CMFCPropertyGridProperty(_T("Environment Mode"), 
        _T(""), _T("Shows the texture layer's environment mode"));

    const EnvModeMap& envModes = getEnvModes();
    for(EnvModeMap::const_iterator it = envModes.begin(); 
        it != envModes.end(); ++it)
    {
        CString str(CA2T(it->first.c_str()));
        m_texLayerEnvModePGP->AddOption(str);
    }
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerEnvModePGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The RGB combination modeproperty

    m_texLayerRgbCombiModePGP = new CMFCPropertyGridProperty(_T("RGB Combi. Mode"), 
        _T(""), _T("Shows the texture layer's RGB combination mode"));
    const RgbCombiModeMap& rgbCombiModes = getRgbCombiModes();
    for(RgbCombiModeMap::const_iterator it = rgbCombiModes.begin(); 
        it != rgbCombiModes.end(); ++it)
    {
        CString str(CA2T(it->first.c_str()));
        m_texLayerRgbCombiModePGP->AddOption(str);
    }
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerRgbCombiModePGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The alpha combination mode property

    m_texLayerAlphaCombiModePGP = new CMFCPropertyGridProperty(_T("Alpha Combi. Mode"), 
        _T(""), _T("Shows the texture layer's alpha combination mode"));
    const AlphaCombiModeMap& alphaCombiModes = getAlphaCombiModes();
    for(AlphaCombiModeMap::const_iterator it = alphaCombiModes.begin(); 
        it != alphaCombiModes.end(); ++it)
    {
        CString str(CA2T(it->first.c_str()));
        m_texLayerAlphaCombiModePGP->AddOption(str);
    }
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerAlphaCombiModePGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The s/t wrap mode property

    m_texLayerSWrapModePGP = new CMFCPropertyGridProperty(_T("S Wrap Mode"), 
        _T(""), _T("Shows the wrap mode for the texture layer's S texture coordinate"));
    m_texLayerTWrapModePGP = new CMFCPropertyGridProperty(_T("T Wrap Mode"), 
        _T(""), _T("Shows the wrap mode for the texture layer's T texture coordinate"));

    const TextureWrapModeMap& wrapModes = getTextureWrapModes();
    for(TextureWrapModeMap::const_iterator it = wrapModes.begin(); 
        it != wrapModes.end(); ++it)
    {
        CString str(CA2T(it->first.c_str()));
        m_texLayerSWrapModePGP->AddOption(str);
        m_texLayerTWrapModePGP->AddOption(str);
    }
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerSWrapModePGP);
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerTWrapModePGP);

    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    // The border color property

   m_texLayerBorderColorPGP = new CMFCPropertyGridColorProperty(
        _T("Border Color"), RGB(0, 0, 0), NULL, 
        _T("Specifies the texture layer's border color"));
    m_texLayerBorderColorPGP->EnableOtherButton(_T("Other..."));
    m_wndPropList[PropertyTypes::TEXTURE_LAYER].AddProperty(m_texLayerBorderColorPGP);

    //m_textureIdPGP = new CMFCPropertyGridProperty(
    //    _T("ID"), _variant_t(short(0)), _T("Shows the texture's id"));
    //m_textureIdPGP->AllowEdit(false);
    

    //// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    //// The file name property

    //m_textureFileNamePGP = new CMFCPropertyGridProperty(
    //    _T("File"), _T(""), _T("Shows the texture's file name"));
    //m_textureFileNamePGP->AllowEdit(false);
    //m_wndPropList[PropertyTypes::TEXTURE].AddProperty(m_textureFileNamePGP);
}

void CPropertiesWnd::createUniformVarPropertyList()
{
    m_uniVarTypePGP = new CMFCPropertyGridProperty(_T("Type"), 
        _T(""), _T("Shows' the uniform var's type"));
    m_wndPropList[PropertyTypes::UNIFORM_VAR].AddProperty(m_uniVarTypePGP);

    // Now comes the tricky part. Handle all the different variable types.
    // For the time being we assume they can be characterized by the number
    // of floats. We'll assume that the maximum number of floats is 16
    // (for a 4x4 float matrix) and create that number of float property
    // controls. Then, from the "update" function, we'll hide those 
    // controls that we don't need (e.g., if the selected type is "float3",
    // we'll have to hide 13 controls)

    m_uniVarFloatsGroupPGP = new CMFCPropertyGridProperty(_T("Value"));
    for(int i = 0; i < MaxUniformVarFloats; ++i)
    {
        CString name, description;
        name.Format(_T("%d"), i);
        description.Format(_T("Shows the uniform var's %d-th component"), i);
        m_uniVarFloatPGP[i] = 
            new CMFCPropertyGridProperty(name, (_variant_t)0.f, description);
        m_uniVarFloatsGroupPGP->AddSubItem(m_uniVarFloatPGP[i]);
    }

    m_wndPropList[PropertyTypes::UNIFORM_VAR].AddProperty(m_uniVarFloatsGroupPGP);
}

void CPropertiesWnd::InitPropList()
{
    SetPropListFont();

    for(int i = 0; i < PropertyTypes::COUNT; ++i)
    {
        m_wndPropList[i].EnableHeaderCtrl(FALSE);
        m_wndPropList[i].EnableDescriptionArea();
        m_wndPropList[i].SetVSDotNetLook();
        m_wndPropList[i].MarkModifiedProperties();
    }

    // ************************************************************************

    createNothingPropertyList();
    createShapePropertyList();
    createLightPropertyList();
    createMaterialPropertyList();
    createShaderProgramPropertyList();
    createShaderPropertyList();
    createTexturePropertyList();
    createTextureLayerPropertyList();
    createUniformVarPropertyList();

    // ************************************************************************
}

void CPropertiesWnd::OnSetFocus(CWnd* pOldWnd)
{
    CDockablePane::OnSetFocus(pOldWnd);
    if(m_activePropertyType != PropertyTypes::NOT_INIT)
    {
        activePropList()->SetFocus();
    }
}

void CPropertiesWnd::OnSettingChange(UINT uFlags, LPCTSTR lpszSection)
{
    CDockablePane::OnSettingChange(uFlags, lpszSection);
    SetPropListFont();
}

void CPropertiesWnd::SetPropListFont()
{
    ::DeleteObject(m_fntPropList.Detach());

    LOGFONT lf;
    afxGlobalData.fontRegular.GetLogFont(&lf);

    NONCLIENTMETRICS info;
    info.cbSize = sizeof(info);

    afxGlobalData.GetNonClientMetrics(info);

    lf.lfHeight = info.lfMenuFont.lfHeight;
    lf.lfWeight = info.lfMenuFont.lfWeight;
    lf.lfItalic = info.lfMenuFont.lfItalic;

    m_fntPropList.CreateFontIndirect(&lf);

    for(int i = 0; i < PropertyTypes::COUNT; ++i)
    {
        m_wndPropList[i].SetFont(&m_fntPropList);
    }
}

// Modified a property field's value so that it lies within the specified range
// Returns the new value


short clampPropertyValue(CMFCPropertyGridProperty * pqp, short minVal, short maxVal)
{
    short value = pqp->GetValue().iVal;
    short clampedValue = value < minVal ? minVal : value > maxVal ? maxVal : value;
    if(clampedValue != value)
    {
        pqp->SetValue(_variant_t(clampedValue));
    }
    return clampedValue;
}

float clampPropertyValue(CMFCPropertyGridProperty * pqp, float minVal, float maxVal)
{
    float value = pqp->GetValue().fltVal;
    float clampedValue = value < minVal ? minVal : value > maxVal ? maxVal : value;
    if(clampedValue != value)
    {
        pqp->SetValue(_variant_t(clampedValue));
    }
    return clampedValue;
}


// Process an "I've changed" notification from one of the three 
// block scale property fields

void CPropertiesWnd::onScaleProperty(GeometryNode& node, Dim dim)
{
    ASSERT(dim >= 0 && dim <= 2);

    float value = clampPropertyValue(m_appearanceScalePGP[dim], 0.f, 1e4f);

    gx::Mat4 transform = node.getLocalTransform();
    gx::Vec3 scale = transform.rotation.get_scale();
    scale[dim] = (double)value / scale[dim];
    transform.rotation = gx::Mat3().make_scale(scale) * transform.rotation;

     postTransformNodeAction(
        getUi().getActionManager(),
        node.getRef<TransformNode>(), 
        transform
    );

}

// Process an "I've changed" notification from one of the three 
// block orientation property fields

void CPropertiesWnd::onOrientationProperty(GeometryNode& node, Dim dim)
{
    ASSERT(dim >= 0 && dim <= 2);
    
    short value = clampPropertyValue(m_appearanceOrientationPGP[dim], 
                                     short(-180), short(180));

    gx::Mat4 transform = node.getLocalTransform();
    gx::Mat3 iniRot = transform.rotation;
    gx::Mat3 modifiedRot = transform.rotation;
    modifiedRot.normalize_cols();
    gx::Vec3 euler = modifiedRot.to_euler();
    euler[dim] = value / 180. * PI;
    //transform.rotation = gx::Mat3().make_rot(euler).scale(iniRot.get_scale());
    transform.rotation = gx::Mat3().make_scale(iniRot.get_scale()) 
                        * gx::Mat3().make_rot(euler);
    
     postTransformNodeAction(
        getUi().getActionManager(),
        node.getRef<TransformNode>(), 
        transform
    );
}

// Process an "I've changed" notification from one of the three 
// block position property fields

void CPropertiesWnd::onPositionProperty(GeometryNode& node, Dim dim)
{
    ASSERT(dim >= 0 && dim <= 2);
    
    double value = (double)m_appearancePositionPGP[dim]->GetValue().fltVal;
    
    gx::Mat4 transform = node.getLocalTransform();
    transform.translation[dim] = value;
    
    postTransformNodeAction(
        getUi().getActionManager(),
        node.getRef<TransformNode>(), 
        transform
    );
}

void CPropertiesWnd::onShapeProperty(CMFCPropertyGridProperty* pgp)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    GeometryNode* node = dynamic_cast<GeometryNode*>(selection);
    ASSERT(node);
/*
    if(pgp == m_appearanceFrontMaterialPGP)
    {
        postMaterialIdAction(
            getUi().getActionManager(), node->getRef<GeometryNode>(),
            pgp->GetValue().iVal, GeometrySides::Front);
        return;
    }

    if(pgp == m_appearanceBackMaterialPGP)
    {
        postMaterialIdAction(
            getUi().getActionManager(), node->getRef<GeometryNode>(),
            pgp->GetValue().iVal, GeometrySides::Back);
        return;
    }
    
    if(pgp == m_appearanceShaderPGP)
    {
        postShaderIdAction(
            getUi().getActionManager(),
            node->getRef<GeometryNode>(),
            pgp->GetValue().iVal);
        return;
    }

    if(pgp == m_appearanceWireframePGP)
    {
        bool value = pgp->GetValue().boolVal != 0;
        postShapeWireframeAction(
            getUi().getActionManager(),
            node->getRef<GeometryNode>(),
            value);
    }
*/
    // TODO when the are several consecutive scale actions in the list (possibly only
    // if they affect the same dimension), retain only the latest one
    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        if(pgp == m_appearanceScalePGP[dim])
        {
            onScaleProperty(*node, Dim(dim));
            return;
        }
    }

    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        if(pgp == m_appearanceOrientationPGP[dim])
        {
            onOrientationProperty(*node, Dim(dim));
            return;
        }
    }

    for(int dim = Dims::X; dim <= Dims::Z; ++dim)
    {
        if(pgp == m_appearancePositionPGP[dim])
        {
            onPositionProperty(*node, Dim(dim));
            return;
        }
    }

    if(pgp == m_appearanceTypePGP)
    {
        // TODO fix this
        //// Create a new geometry based on its type as a string
        //CT2A ansi((CString)pgp->GetValue());
        //ref<Geometry> newGeo = GeometryFactory::geometryFromName((LPCSTR)ansi);

        //// Copy base class part of current shape (wireframe, color, ...)
        //// TODO this is horrible, should be a method (copyBaseProperties())
        //ref<const Geometry> current = node->getGeometry();
        //*newGeo = *current;
        //
        //// post the actual action
        //postGeometryTypeAction(
        //    getUi().getActionManager(),
        //    node->getRef<GeometryNode>(), 
        //    newGeo);
    }
}


void CPropertiesWnd::onLightProperty(CMFCPropertyGridProperty* pgp)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    LightNode* light = dynamic_cast<LightNode*>(selection);
    ASSERT(light);

    LightParams params = light->getParams();
    bool mustUpdate = true;
    bool mustUpdatePropertyList = false;

    if(pgp == m_lightAmbientColorPGP)
    {
        Color color = convertColor(m_lightAmbientColorPGP->GetColor());
        //ActionManager::instance().postAction(ref<Action>(
        //    new ShapeColorAction(
        //    SelectionManager::instance().getSelectedNode(), color)));
        params.ambientColor = color;
    }
    else if(pgp == m_lightDiffuseColorPGP)
    {
        Color color = convertColor(m_lightDiffuseColorPGP->GetColor());
        //ActionManager::instance().postAction(ref<Action>(
        //    new ShapeColorAction(
        //    SelectionManager::instance().getSelectedNode(), color)));
        params.diffuseColor = color;
    }
    else if(pgp == m_lightSpecularColorPGP)
    {
        Color color = convertColor(m_lightSpecularColorPGP->GetColor());
        //ActionManager::instance().postAction(ref<Action>(
        //    new ShapeColorAction(
        //    SelectionManager::instance().getSelectedNode(), color)));
        params.specularColor = color;
    }
    else if(pgp == m_lightTypePGP)
    {
        // TODO take the whole shape => name / name => 
        // shape stuff to a factory class
        CString selected = pgp->GetValue();
        if(selected == _T("Positional"))
        {
            params.type = LightTypes::POSITIONAL;
        }
        else 
        {
            ASSERT(selected == _T("Directional"));
            params.type = LightTypes::DIRECTIONAL;
        }

        // Must update bec. some fields only apply to either pos or dir
        mustUpdatePropertyList = true;

    }
    else if(pgp == m_lightSpotEnablePGP)
    {
        ASSERT(light->getType() == LightTypes::POSITIONAL);
        CString selected = pgp->GetValue();
        params.isSpot = selected == _T("Yes");
        
        // Must update to make the cutoff field appear
        mustUpdatePropertyList = true;
    }
    else if(pgp == m_lightSpotCutoffPGP)
    {
        ASSERT(light->getType() == LightTypes::POSITIONAL);
        ASSERT(light->isSpot());
        short cutoff = ::clampPropertyValue(pgp, short(0), short(100));
        params.spotCutoff = cutoff;
    }
    else if(pgp == m_lightConstantAttenuationPGP)
    {
        ASSERT(light->getType() == LightTypes::POSITIONAL);
        short factor = ::clampPropertyValue(pgp, short(0), short(1000));
        float attenuation = factor / 100.f;
        params.constantAttenuationFactor = attenuation;
    }
    else if(pgp == m_lightLinearAttenuationPGP)
    {
        ASSERT(light->getType() == LightTypes::POSITIONAL);
        short factor = ::clampPropertyValue(pgp, short(0), short(1000));
        float attenuation = factor / 100.f;
        params.linearAttenuationFactor = attenuation;
    }
    else if(pgp == m_lightQuadraticAttenuationPGP)
    {
        ASSERT(light->getType() == LightTypes::POSITIONAL);
        short factor = ::clampPropertyValue(pgp, short(0), short(1000));
        float attenuation = factor / 100.f;
        params.quadraticAttenuationFactor = attenuation;
    }
    else
    {
        mustUpdate = false;
    }

    if(mustUpdate)
    {
        postLightParamsAction(
            getUi().getActionManager(),
            light->getRef<LightNode>(),
            params);

        if(mustUpdatePropertyList)
        {
            updateLightPropertyList(*light);    
        }
    }
}

void CPropertiesWnd::onMaterialProperty(CMFCPropertyGridProperty* pgp)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    Material* mat = dynamic_cast<Material*>(selection);
    ASSERT(mat);

    MaterialParams params = mat->getParams();
    bool mustUpdate = true;

    if(pgp == m_materialAmbientColorPGP)
    {
        Color color = convertColor(m_materialAmbientColorPGP->GetColor());
        params.ambientColor = color;
    }
    else if(pgp == m_materialDiffuseColorPGP)
    {
        Color color = convertColor(m_materialDiffuseColorPGP->GetColor());
        params.diffuseColor = color;
    }
    else if(pgp == m_materialSpecularColorPGP)
    {
        Color color = convertColor(m_materialSpecularColorPGP->GetColor());
        params.specularColor = color;
    }
    else if(pgp == m_materialShininessPGP)
    {
        short shininess = ::clampPropertyValue(pgp, short(0), short(100));
        params.shininess = shininess / 100.f;
    }
    else if(pgp == m_materialTransparentPGP)
    {
        params.transparent = pgp->GetValue().boolVal != 0;
    }
    else
    {
        mustUpdate = false;
    }

    if(mustUpdate)
    {
        postMaterialParamsAction(
            getUi().getActionManager(),
            mat->getRef<Material>(),
            params);
        
        m_wndMaterialPreview.Invalidate();
    }
}

void CPropertiesWnd::onShaderProgramProperty(CMFCPropertyGridProperty*)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    ShaderProgram* shader = dynamic_cast<ShaderProgram*>(selection);
    ASSERT(shader != NULL);
}

void CPropertiesWnd::onTextureProperty(CMFCPropertyGridProperty*)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    Texture* tex = dynamic_cast<Texture*>(selection);
    ASSERT(tex != NULL);
}

void CPropertiesWnd::onTextureLayerProperty(CMFCPropertyGridProperty* pgp)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    TextureLayer* texLayer = dynamic_cast<TextureLayer*>(selection);
    ASSERT(texLayer != NULL);

    if(pgp == m_texLayerTexIdPGP)
    {
        short id = pgp->GetValue().iVal;
        TextureManager& tm = getWorld().getTextureManager();
        Texture* tex = tm.getTextureById(id);
        texLayer->setTexture(tex->getRef<Texture>());
    }
    else if(pgp == m_texLayerEnvModePGP)
    {
        const EnvModeMap& envModes = getEnvModes();

        CString val = pgp->GetValue();
        CT2A ansi(val);

        EnvModeMap::const_iterator it = envModes.find(std::string(ansi));
        if(it != envModes.end())
        {
            texLayer->setEnvMode(it->second);
        }
        else
        {
            ASSERT(false);
        }
    }
    else if(pgp == m_texLayerRgbCombiModePGP)
    {
        const RgbCombiModeMap& rgbCombiModes = getRgbCombiModes();

        CString val = pgp->GetValue();
        CT2A ansi(val);

        RgbCombiModeMap::const_iterator it = rgbCombiModes.find(std::string(ansi));
        if(it != rgbCombiModes.end())
        {
            texLayer->setRgbCombiMode(it->second);
        }
        else
        {
            ASSERT(false);
        }
    }
    else if(pgp == m_texLayerAlphaCombiModePGP)
    {
        const AlphaCombiModeMap& alphaCombiModes = getAlphaCombiModes();

        CString val = pgp->GetValue();
        CT2A ansi(val);

        AlphaCombiModeMap::const_iterator it = alphaCombiModes.find(std::string(ansi));
        if(it != alphaCombiModes.end())
        {
            texLayer->setAlphaCombiMode(it->second);
        }
        else
        {
            ASSERT(false);
        }
    }    
    else if(pgp == m_texLayerSWrapModePGP || pgp == m_texLayerTWrapModePGP)
    {
        const TextureWrapModeMap& wrapModes = getTextureWrapModes();

        CString val = pgp->GetValue();
        CT2A ansi(val);

        TextureWrapModeMap::const_iterator it = 
            wrapModes.find(std::string(ansi));
        if(it != wrapModes.end())
        {
            if(pgp == m_texLayerSWrapModePGP)
            {
                texLayer->setSWrapMode(it->second);
            }
            else
            {
                texLayer->setTWrapMode(it->second);
            }
        }
        else
        {
            ASSERT(false);
        }
    }   
    else if(pgp == m_texLayerBorderColorPGP)
    {
        Color color = convertColor(m_texLayerBorderColorPGP->GetColor());
        texLayer->setBorderColor(color);
    }
}

void CPropertiesWnd::onUniformVarProperty(CMFCPropertyGridProperty* pgp)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    UniformVar* uniVar = dynamic_cast<UniformVar*>(selection);
    ASSERT(uniVar != NULL);

    int i;
    for(i = 0; i < MaxUniformVarFloats; ++i)
    {
        if(pgp == m_uniVarFloatPGP[i])
        {
            break;
        }
    }
    ASSERT(i < MaxUniformVarFloats);

    float val = pgp->GetValue().fltVal;
    uniVar->setFloat(i, val);
}


// Process an "I've changed" notification from one the property fields

// See http://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/8922d2a5-eeef-4a86-81a0-b3ffc2d55dd1
// Parameters:
// [in] wparam: the control ID of the CMFCPropertyGridCtrl that changed.
// [in] lparam: pointer to the CMFCPropertyGridProperty that changed.
LRESULT CPropertiesWnd::OnPropertyChanged(WPARAM wparam, LPARAM lparam )
{

    int id = (int)wparam;
    if(m_wndPropList[PropertyTypes::LIGHT].GetDlgCtrlID() == id)
    {
        onLightProperty((CMFCPropertyGridProperty*)lparam);
    }
    
    else if(m_wndPropList[PropertyTypes::MATERIAL].GetDlgCtrlID() == id)
    {
        onMaterialProperty((CMFCPropertyGridProperty*)lparam);
    }

    else if(m_wndPropList[PropertyTypes::SHAPE].GetDlgCtrlID() == id)
    {
        onShapeProperty((CMFCPropertyGridProperty*)lparam);
    }

    else if(m_wndPropList[PropertyTypes::SHADER_PROGRAM].GetDlgCtrlID() == id)
    {
        onShaderProgramProperty((CMFCPropertyGridProperty*)lparam);
    }

    else if(m_wndPropList[PropertyTypes::TEXTURE].GetDlgCtrlID() == id)
    {
        onTextureProperty((CMFCPropertyGridProperty*)lparam);
    }
    
    else if(m_wndPropList[PropertyTypes::TEXTURE_LAYER].GetDlgCtrlID() == id)
    {
        onTextureLayerProperty((CMFCPropertyGridProperty*)lparam);
    }

    else if(m_wndPropList[PropertyTypes::UNIFORM_VAR].GetDlgCtrlID() == id)
    {
        onUniformVarProperty((CMFCPropertyGridProperty*)lparam);
    }

    return 0; 
}

 void CPropertiesWnd::onWorldChanged(World* newWorld)
 {
     m_wndMaterialPreview.setWorld(newWorld);
 }

 void CPropertiesWnd::onUiChanged(Ui* newUi)
 {
     m_wndMaterialPreview.setUi(newUi);
 }

void CPropertiesWnd::onSelectionChanged(SelectionManager*)
{
    updateActivePropertyList();
}

void CPropertiesWnd::onNodeEvent(const NodeEvent& event_)
{
    Selectable* selection = getUi().getSelectionManager().getSelection();
    if((Selectable*)event_.node == selection)
    {
        TransformNode* node = dynamic_cast<TransformNode*>(selection);
        if(node != NULL)
        {
            // If we have detailed notification info., take advantage of it
            // in order to update only those controls that are likely to have
            // changed
            switch(event_.property_)
            {
            case NodeEvent::NodeOrientation:
                updateGeometryOrientationControls(node->getLocalTransform());
                return;
            case NodeEvent::NodePosition:
                updateGeometryPositionControls(node->getLocalTransform());
                return;
            case NodeEvent::NodeScale:
                updateGeometryScaleControls(node->getLocalTransform());
                return;
            }
        }
        updateActivePropertyList();
    }
}

void CPropertiesWnd::onShaderEvent(const ShaderEvent& event_)
{
    if(event_.type == ShaderEvent::UniformVariableChanged)
    {
        if(m_activePropertyType == PropertyTypes::UNIFORM_VAR)
        {
            Selectable* selection = 
                getUi().getSelectionManager().getSelection();
            UniformVar* uniVar = dynamic_cast<UniformVar*>(selection);
            if(uniVar != NULL && uniVar->getProgram() == event_.program)
            {
                updateActivePropertyList();
            }
        }
    }
    else 
    {
        if(m_activePropertyType == PropertyTypes::SHADER_PROGRAM 
           || m_activePropertyType == PropertyTypes::SHADER)
        {  
            updateActivePropertyList();
        }
    }
}
